package utils

import (
	"fmt"
	amqp "github.com/rabbitmq/amqp091-go"
	"sync"
	"time"
)

type AmqpClient struct {
	url         string
	Connection  *amqp.Connection
	channelPool sync.Pool
	lock        sync.RWMutex
}

func NewAmqpClient(url string) (*AmqpClient, error) {
	conn, err := amqp.Dial(url)
	if err != nil {
		return nil, err
	}
	ac := &AmqpClient{
		url:        url,
		Connection: conn,
	}
	ac.channelPool = sync.Pool{New: func() any {
		var err error
		var ch *amqp.Channel
		if ac.Connection.IsClosed() {
			ac.lock.Lock()
			ac.Connection, err = amqp.Dial(ac.url)
			ac.lock.Unlock()
			if err != nil {
				return err
			}
		}
		ac.lock.RLock()
		defer ac.lock.RUnlock()
		if err = Retry(5, 5*time.Microsecond, func() error {
			if ch, err = ac.Connection.Channel(); err != nil {
				return err
			}
			return nil
		}); err != nil {
			return err
		}
		return ch
	}}
	go func(c *AmqpClient) {
		ch, _ := c.GetChannel()
		time.Sleep(3 * time.Second)
		c.PutChannel(ch)
	}(ac)
	return ac, nil
}

func (ac *AmqpClient) Close() error {
	if ac.Connection.IsClosed() {
		return nil
	}
	return ac.Connection.Close()
}

func (ac *AmqpClient) GetChannel() (*amqp.Channel, error) {
	var ch *amqp.Channel
	var ok bool
	var err error
	if err = Retry(5, 5*time.Microsecond, func() error {
		ich := ac.channelPool.Get()
		if ch, ok = ich.(*amqp.Channel); ok {
			if ch.IsClosed() {
				return fmt.Errorf(`channel is closed`)
			}
		} else if err, ok = ich.(error); ok {
			return err
		}
		return nil
	}); err != nil {
		return nil, err
	}
	return ch, nil
}

func (ac *AmqpClient) PutChannel(ch *amqp.Channel) {
	if ch == nil || ch.IsClosed() {
		return
	}
	ac.channelPool.Put(ch)
}
